// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EV4Manifest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Test.Robotics.Hardware.EV4
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    using Microsoft.Robotics.Hardware.DAC;
    using Microsoft.Robotics.Hardware.EV4.Runtime;
    using Microsoft.Robotics.Hardware.Runtime.DAC;
    using Microsoft.Robotics.Runtime;
    using Microsoft.Robotics.Visualization;
    using Test.Robotics;

    /// <summary>
    /// Provides methods for creating manifests in a type-checked way.
    /// </summary>
    public static class EV4Manifest
    {
        /// <summary>
        /// The set of heartbeat messages interesting for recording or remoting
        /// </summary>
        private static Type[] ev4SensorMessageTypes = new[] 
            { 
                typeof(AccelerometerMessage), 
                typeof(CompassMessage), 
                typeof(GyroscopeMessage), 
                typeof(SonarMessage),
                typeof(IRProximityMessage),
                typeof(DifferentialDriveMessage), 
                typeof(BatteryMessage),
                typeof(JointMessage),
            };

        /// <summary>
        /// The set of heartbeat messages interesting for playing back existing recordings (2/14/2013)
        /// </summary>
        private static Type[] ev4LegacySensorMessageTypes = new[] 
            { 
                typeof(AccelerometerMessage), 
                typeof(CompassMessage), 
                typeof(GyroscopeMessage), 
                typeof(SonarMessage),
                typeof(IRProximityMessage),
                typeof(DifferentialDriveMessage), 
                typeof(BatteryMessage),
                typeof(IMUMessage)
            };

        /// <summary>
        /// The set of EV4 control messages interesting for recording or remoting
        /// </summary>
        private static Type[] ev4ControlMessageTypes = new[] 
            { 
                typeof(DifferentialDriveSetVelocityMessage)
            };

        /// <summary>
        /// Adds the EV4 agent that surfaces all the sensors.
        /// </summary>
        /// <param name="manifest">The manifest to add to</param>
        /// <param name="views">The set of views to add any visualization to</param>
        /// <param name="robotConfigFolder">Robot config folder</param>
        /// <returns>The EV4 heartbeat agent</returns>
        public static HeartbeatAgent AddEV4HeartbeatAgent(Manifest manifest, List<ViewDefinition> views, string robotConfigFolder)
        {
            HeartbeatAgent heartbeat = new HeartbeatAgent(
                name: "HeartbeatAgent",
                driveCommandRenewalPeriod: 500,
                robotConfigFolder: robotConfigFolder);
            manifest.Agents.Add(heartbeat);

            return heartbeat;
        }

        /// <summary>
        /// Adds a file recorder proxy for the heartbeat agent to the specified manifest.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="heartbeat">The agent to record.</param>
        /// <returns>A file recorder proxy</returns>
        public static FileRecorderProxy AddEV4HeartbeatRecorder(Manifest manifest, HeartbeatAgent heartbeat)
        {
            FileRecorderProxy heartbeatRecorder = new FileRecorderProxy(
                name: heartbeat.Name + "Recorder",
                filePath: null,
                messageTypes: EV4Manifest.ev4SensorMessageTypes,
                producer: heartbeat.Name);
            manifest.Agents.Add(heartbeatRecorder);
            return heartbeatRecorder;
        }

        /// <summary>
        /// Adds a remoting proxy to broadcast heartbeat agent messages over the network.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="heartbeat">The agent to network-enable.</param>
        /// <param name="desktopNameOrAddress">The name or IP address of the machine to broadcast to (e.g. a desktop machine)</param>
        /// <returns>A remoting proxy</returns>
        public static ProxyDefinition AddEV4HeartbeatNetworkEnabledProxy(Manifest manifest, HeartbeatAgent heartbeat, string desktopNameOrAddress)
        {
            ProxyDefinition heartbeatProxy = ProxyDefinition.CreateNetworkEnabledProxy(
                name: heartbeat.Name + "Proxy",
                producer: heartbeat.Name,
                address: desktopNameOrAddress,
                publishContracts: EV4Manifest.ev4SensorMessageTypes,
                publishNetworkPortNumber: (int)RemotingPorts.EV4HeartbeatPublish,
                controlContracts: EV4Manifest.ev4ControlMessageTypes,
                controlNetworkPortNumber: (int)RemotingPorts.EV4HeartbeatControl);
            manifest.NetworkEnabledAgentProxies.Add(heartbeatProxy);

            return heartbeatProxy;
        }

        /// <summary>
        /// Adds a playback proxy to the pipeline that replays the specified control board recording.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="timer">The timer driving the playback</param>
        /// <param name="directory">The directory containing the file to play back</param>
        /// <param name="fileName">Optional. The name of the recorded file to play back.</param>
        /// <returns>The heartbeat producer interface</returns>
        public static FilePlayerProxy AddEV4HeartbeatPlayback(Manifest manifest, List<ViewDefinition> views, TimerAgent timer, string directory, string fileName = "HeartbeatAgent.xml")
        {
            FilePlayerProxy playbackProxy = new FilePlayerProxy(
                name: "HeartbeatAgent",
                filePath: Path.Combine(directory, fileName),
                messageTypes: EV4Manifest.ev4SensorMessageTypes,
                timerAgent: timer);
            manifest.Agents.Add(playbackProxy);

            return playbackProxy;
        }

        /// <summary>
        /// Adds a playback proxy to the pipeline that replays the specified drive feedback recording.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="timer">The timer driving the playback</param>
        /// <param name="directory">The directory containing the file to play back</param>
        /// <param name="fileName">Optional. The name of the recorded file to play back.</param>
        /// <returns>The drive feedback producer interface</returns>
        public static FilePlayerProxy AddDriveFeedbackPlayback(Manifest manifest, List<ViewDefinition> views, TimerAgent timer, string directory, string fileName = "DriveFeedbackAgent.xml")
        {
            FilePlayerProxy playbackProxy = new FilePlayerProxy(
                name: "DriveFeedback",
                filePath: Path.Combine(directory, fileName),
                messageTypes: new Type[] { typeof(SimpleAgentMessage<DriveFeedbackState>) },
                timerAgent: timer);
            manifest.Agents.Add(playbackProxy);

            return playbackProxy;
        }

        /// <summary>
        /// Adds a playback proxy to the pipeline that replays a legacy control board recording (2/14/2013).
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="timer">The timer driving the playback</param>
        /// <param name="directory">The directory containing the file to play back</param>
        /// <param name="fileName">Optional. The name of the recorded file to play back.</param>
        /// <returns>The heartbeat producer interface</returns>
        public static FilePlayerProxy AddLegacyEV4HeartbeatPlayback(Manifest manifest, List<ViewDefinition> views, TimerAgent timer, string directory, string fileName = "HeartbeatAgent.xml")
        {
            FilePlayerProxy playbackProxy = new FilePlayerProxy(
                name: "HeartbeatAgent",
                filePath: Path.Combine(directory, fileName),
                messageTypes: EV4Manifest.ev4LegacySensorMessageTypes,
                timerAgent: timer);
            manifest.Agents.Add(playbackProxy);

            return playbackProxy;
        }

        /// <summary>
        /// Adds a playback proxy to the pipeline that replays the specified control board recording.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="robotNameOrAddress">The name or IP address of the machine to broadcast to</param>
        /// <param name="robotConfigFolder">Robot config folder</param>
        /// <returns>The heartbeat producer interface</returns>
        public static HeartbeatAgent AddEV4RemoteProxy(Manifest manifest, List<ViewDefinition> views, string robotNameOrAddress, string robotConfigFolder)
        {
            // create the heartbeat agent, but remove it from the manifest and replace it with a proxy with the same name
            // We create it because need to return the right set of producer interfaces, with the heartbeat agent implements
            HeartbeatAgent fake = EV4Manifest.AddEV4HeartbeatAgent(manifest, views, robotConfigFolder);
            manifest.Agents.Remove(fake);

            // now create a proxy with the same name and add it to the manifest
            ProxyDefinition remoteProxy = ProxyDefinition.CreateRemoteProxy(
                name: fake.Name,
                address: robotNameOrAddress,
                publishContracts: EV4Manifest.ev4SensorMessageTypes,
                publishNetworkPortNumber: (int)RemotingPorts.EV4HeartbeatPublish,
                controlContracts: EV4Manifest.ev4ControlMessageTypes,
                controlNetworkPortNumber: (int)RemotingPorts.EV4HeartbeatControl);
            manifest.RemoteAgentProxies.Add(remoteProxy);

            // return the fake agent, so that other manifest-creation functions get the right type
            // since the only things used from the fake agent are the IProducer interfaces and the agent name, this is ok
            return fake;
        }
    }
}
